package org.infodavid.common.impl.persistence;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.persistence.PersistenceException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.Criteria;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.joda.time.DateTime;

import org.infodavid.common.annotations.AImplementation;
import org.infodavid.common.annotations.AImplementation.ERuntime;
import org.infodavid.common.dto.IUserGroupListItem;
import org.infodavid.common.dto.IUserGroupListItem.EUserRole;
import org.infodavid.common.ejb.UserGroupBean;
import org.infodavid.common.model.IUser;
import org.infodavid.common.model.IUserGroup;
import org.infodavid.common.model.criteria.IExtendedSearchCriteria;
import org.infodavid.common.model.criteria.IUserGroupCriteria;
import org.infodavid.common.model.impl.HibernateHelper;
import org.infodavid.common.persistence.IQueryParameter;
import org.infodavid.common.persistence.PersistenceSession;
import org.infodavid.common.persistence.QueryParameterFactory;
import org.infodavid.common.persistence.UserDataService;
import org.infodavid.common.persistence.UserGroupDataService;
import org.infodavid.common.persistence.impl.DataServiceHelper;
import org.infodavid.common.persistence.impl.PersistenceUtil;
import org.infodavid.common.services.exceptions.ServiceException;

/**
 * The Class UserGroupDataServiceImpl.
 */
@AImplementation(value = UserGroupDataService.class, runtime = ERuntime.DEFAULT)
public final class UserGroupDataServiceImpl extends AbstractUserGroupDataService {

  /** The Constant log. */
  private static final Log LOGGER = LogFactory.getLog(AbstractUserGroupDataService.class);

  /** The helper. */
  private final DataServiceHelper<IUserGroupListItem,IUserGroup,Long> helper;

  /**
   * The Constructor.
   */
  private UserGroupDataServiceImpl() {
    super();

    helper = new DataServiceHelper<IUserGroupListItem,IUserGroup,Long>(this);
  }

  /*
   * (non-Javadoc)
   * @see org.infodavid.common.persistence.IDataService#getEntityClass()
   */
  public Class<? extends IUserGroup> getEntityClass() {
    return UserGroupBean.class;
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.persistence.GroupDataService#findByName(org.infodavid.common.persistence.
   * PersistenceSession, java.lang.String)
   */
  @Override
  public IUserGroup findByName(final PersistenceSession session, final String value)
      throws PersistenceException, ServiceException {
    return helper.findByName(session, value, null, QUERY_FIND_BY_NAME);
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see
   * org.infodavid.common.persistence.GroupDataService#findByGuest(org.infodavid.common.persistence.
   * PersistenceSession, boolean, java.util.List, org.infodavid.common.model.criteria.ISearchCriteria)
   */
  @Override
  public long findByGuest(final PersistenceSession session, final boolean flag,
      final List<IUserGroup> results, final IExtendedSearchCriteria criteria) throws ServiceException,
      PersistenceException {
    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("Searching by guest using session = " + session + ", entities = " + results
          + ", criteria = " + criteria + ", flag = " + flag);
    }

    PersistenceUtil.validate(session);
    helper.validate(results);

    final QueryParameterFactory paramFactory = QueryParameterFactory.getSingleton();
    final List<IQueryParameter> params = new ArrayList<IQueryParameter>();
    final IQueryParameter parameter = paramFactory.borrow();

    parameter.setName(IUserGroup.GUEST);
    parameter.setValue(Boolean.valueOf(flag));
    params.add(parameter);

    PersistenceUtil.applyCriteria(params, criteria);

    try {
      return helper.find(session, results, params, QUERY_FIND_BY_GUEST, QUERY_FIND_BY_GUEST_COUNT);
    }
    finally {
      PersistenceUtil.releaseQueryParameters(params);
    }
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.persistence.GroupDataService#findByRole(org.infodavid.common.persistence.
   * PersistenceSession, org.infodavid.common.model.IGroup.EGroupRole, java.util.List,
   * org.infodavid.common.model.criteria.ISearchCriteria)
   */
  @Override
  public long findByRole(final PersistenceSession session, final EUserRole role,
      final List<IUserGroup> results, final IExtendedSearchCriteria criteria) throws ServiceException,
      PersistenceException {
    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("Searching by role using session = " + session + ", entities = " + results
          + ", criteria = " + criteria + ", role = " + role);
    }

    PersistenceUtil.validate(session);
    helper.validate(results);

    final QueryParameterFactory paramFactory = QueryParameterFactory.getSingleton();
    final List<IQueryParameter> params = new ArrayList<IQueryParameter>();

    final IQueryParameter parameter = paramFactory.borrow();

    parameter.setName(IUserGroup.ROLE);
    parameter.setValue(role);
    params.add(parameter);

    PersistenceUtil.applyCriteria(params, criteria);

    try {
      return helper.find(session, results, params, QUERY_FIND_ACTIVE_BY_ROLE,
          QUERY_FIND_ACTIVE_BY_ROLE_COUNT);
    }
    finally {
      PersistenceUtil.releaseQueryParameters(params);
    }
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.persistence.GroupDataService#find(org.infodavid.common.persistence.
   * PersistenceSession, java.util.List, org.infodavid.common.model.criteria.IGroupCriteria)
   */
  @Override
  @SuppressWarnings("unchecked")
  public long find(final PersistenceSession session, final List<IUserGroupListItem> entities,
      final IUserGroupCriteria criteria) throws PersistenceException {
    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("Searching by criteria using session = " + session + ", entities = " + entities
          + ", criteria = " + criteria);
    }

    PersistenceUtil.validate(session);
    helper.validate(entities);

    final List<IQueryParameter> params = new ArrayList<IQueryParameter>();
    final HibernateHelper hbnHelper = HibernateHelper.getInstance();
    final Criteria hbnCriteria = hbnHelper.createCriteria(session, UserGroupBean.class);
    final Criteria hbnCountCriteria = hbnHelper.createCriteria(session, UserGroupBean.class);

    if (criteria == null) {
      hbnCriteria.add(Restrictions.eq(IUserGroup.ARCHIVED, Boolean.FALSE));
      hbnCountCriteria.add(Restrictions.eq(IUserGroup.ARCHIVED, Boolean.FALSE));
    }
    else {
      final Boolean archived = criteria.isArchived();
      final String name = criteria.getNamePattern();
      final EUserRole role = criteria.getRole();

      if (StringUtils.isNotEmpty(name)) {
        hbnCriteria.add(Restrictions.like(IUserGroup.NAME, name));
        hbnCountCriteria.add(Restrictions.like(IUserGroup.NAME, name));
      }

      if (role != null) {
        hbnCriteria.add(Restrictions.like(IUserGroup.ROLE, role));
        hbnCountCriteria.add(Restrictions.like(IUserGroup.ROLE, role));
      }

      if (archived == null) {
        hbnCriteria.add(Restrictions.eq(IUserGroup.ARCHIVED, Boolean.FALSE));
        hbnCountCriteria.add(Restrictions.eq(IUserGroup.ARCHIVED, Boolean.FALSE));
      }
      else {
        hbnCriteria.add(Restrictions.eq(IUserGroup.ARCHIVED, archived));
        hbnCountCriteria.add(Restrictions.eq(IUserGroup.ARCHIVED, archived));
      }
    }

    PersistenceUtil.applyCriteria(params, criteria);
    hbnHelper.applyParameters(params, hbnCriteria);

    try {
      hbnCountCriteria.setProjection(Projections.count(IUserGroup.KEY));
      session.flush();

      final long count = ((Number)hbnCountCriteria.uniqueResult()).longValue();

      if (count > 0) {
        entities.addAll(hbnCriteria.list());
      }

      return count;
    }
    finally {
      PersistenceUtil.releaseQueryParameters(params);
    }
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see
   * org.infodavid.common.persistence.IDataService#clear(org.infodavid.common.persistence.PersistenceSession
   * , java.util.List)
   */
  public void clear(final PersistenceSession session, final List<IQueryParameter> parameters)
      throws PersistenceException {
    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("Removing entities using session = " + session + ", parameters = " + parameters);
    }

    session.removeEntities(session.findEntities(IUserGroup.class, QUERY_FIND, parameters), null);
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see
   * org.infodavid.common.persistence.IDataService#count(org.infodavid.common.persistence.PersistenceSession
   * , java.util.List)
   */
  public long count(final PersistenceSession session, final List<IQueryParameter> parameters)
      throws PersistenceException {
    return helper.count(session, parameters, QUERY_FIND_COUNT);
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see
   * org.infodavid.common.persistence.IDataService#find(org.infodavid.common.persistence.PersistenceSession
   * , java.util.List, java.util.List)
   */
  public long find(final PersistenceSession session, final List<IUserGroupListItem> entities,
      final List<IQueryParameter> parameters) throws PersistenceException {
    return helper.findListItems(session, entities, parameters, QUERY_FIND, QUERY_FIND_COUNT);
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see
   * org.infodavid.common.persistence.IDataService#findByCreationDate(org.infodavid.common.persistence
   * .PersistenceSession, org.joda.time.DateTime, org.joda.time.DateTime, java.util.List,
   * java.util.List)
   */
  public long findByCreationDate(final PersistenceSession session, final DateTime date1,
      final DateTime date2, final List<IUserGroupListItem> entities,
      final List<IQueryParameter> parameters) throws PersistenceException {
    PersistenceUtil.validate(session);

    final List<IQueryParameter> params = PersistenceUtil.initializeQueryParameters(parameters);

    try {
      PersistenceUtil.applySorting(params, IUserGroup.CREATION_DATE);

      return helper.findListItemsByDate(session, date1, date2, entities, params,
          QUERY_FIND_BY_CREATION_DATE, QUERY_FIND_BY_CREATION_DATE_COUNT);
    }
    finally {
      PersistenceUtil.releaseQueryParameters(params);
    }
  }

  /*
   * See super class or interface. (nonIUser-Javadoc)
   * @see
   * org.infodavid.common.persistence.IDataService#findByModificationDate(org.infodavid.common.persistence
   * .PersistenceSession, org.joda.time.DateTime, org.joda.time.DateTime, java.util.List,
   * java.util.List)
   */
  public long findByModificationDate(final PersistenceSession session, final DateTime date1,
      final DateTime date2, final List<IUserGroupListItem> entities,
      final List<IQueryParameter> parameters) throws PersistenceException {
    PersistenceUtil.validate(session);

    final List<IQueryParameter> params = PersistenceUtil.initializeQueryParameters(parameters);

    try {
      PersistenceUtil.applySorting(params, IUserGroup.MODIFICATION_DATE);

      return helper.findListItemsByDate(session, date1, date2, entities, params,
          QUERY_FIND_BY_MODIFICATION_DATE, QUERY_FIND_BY_MODIFICATION_DATE_COUNT);
    }
    finally {
      PersistenceUtil.releaseQueryParameters(params);
    }
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.persistence.GroupDataService#addMembers(org.infodavid.common.persistence.
   * PersistenceSession, long, long[])
   */
  @Override
  public void addMembers(final PersistenceSession session, final long groupKey,
      final long... members) throws ServiceException, PersistenceException {
    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("Adding members using session = " + session + ", groupKey = " + groupKey
          + ", members = " + Arrays.toString(members));
    }

    PersistenceUtil.validate(session);
    helper.validate(Long.valueOf(groupKey));

    final UserDataService userDataService = UserDataService.getInstance();
    final IUserGroup group = findByPrimaryKey(session, Long.valueOf(groupKey));
    IUser member;

    for (final long memberKey : members) {
      member = userDataService.findByPrimaryKey(session, Long.valueOf(memberKey));
      member.addGroup(group);
    }
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see
   * org.infodavid.common.persistence.GroupDataService#removeMembers(org.infodavid.common.persistence
   * .PersistenceSession, long, long[])
   */
  @Override
  public void removeMembers(final PersistenceSession session, final long groupKey,
      final long... members) throws ServiceException, PersistenceException {
    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("Removing members using session = " + session + ", groupKey = " + groupKey
          + ", members = " + Arrays.toString(members));
    }

    PersistenceUtil.validate(session);
    helper.validate(Long.valueOf(groupKey));

    final UserDataService userDataService = UserDataService.getInstance();
    final IUserGroup group = findByPrimaryKey(session, Long.valueOf(groupKey));
    IUser member;

    for (final long memberKey : members) {
      member = userDataService.findByPrimaryKey(session, Long.valueOf(memberKey));
      member.removeGroup(group);
    }
  }
}
